package me.tonywang.security.util;

/*
 * =============================================================================
 *
 *   Copyright (c) 2011-2016, The THYMELEAF team (http://www.thymeleaf.org)
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 * =============================================================================
 */

import org.springframework.context.ApplicationContext;
import org.springframework.core.GenericTypeResolver;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.access.expression.SecurityExpressionHandler;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.WebAttributes;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.thymeleaf.exceptions.TemplateProcessingException;
import org.thymeleaf.extras.springsecurity4.util.SpringSecurityWebApplicationContextUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;



/**
 * @author Daniel Fern&aacute;ndez
 */
public final class AuthUtils {

    private AuthUtils() {
        super();
    }


    public static Authentication getAuthenticationObject() {

        return org.thymeleaf.extras.springsecurity4.auth.AuthUtils.getAuthenticationObject();

    }


    public static Object getAuthenticationProperty(final Authentication authentication, final String property) {

        return org.thymeleaf.extras.springsecurity4.auth.AuthUtils.getAuthenticationProperty(authentication, property);

    }


    public static boolean authorizeUsingAccessExpression(
            final String accessExpression, final Authentication authentication,
            final HttpServletRequest request, final HttpServletResponse response,
            final ServletContext servletContext) {

        /*
         * In case this expression is specified as a standard variable expression (${...}), clean it.
         */
        final String expr =
                ((accessExpression != null && accessExpression.startsWith("${") && accessExpression.endsWith("}")) ?
                        accessExpression.substring(2, accessExpression.length() - 1) :
                        accessExpression);

        final SecurityExpressionHandler<FilterInvocation> handler = getExpressionHandler(servletContext);

        Expression expressionObject   = handler.getExpressionParser().parseExpression(expr);

        final FilterInvocation filterInvocation = new FilterInvocation(request, response, new FilterChain() {
            public void doFilter(ServletRequest request, ServletResponse response) {
                throw new UnsupportedOperationException();
            }
        });

        final EvaluationContext evaluationContext = handler.createEvaluationContext(authentication, filterInvocation);


        // We add Thymeleaf's wrapper on top of the SpringSecurity basic evaluation context
        // We need to do this through a version-independent wrapper because the classes we will use for the
        // EvaluationContext wrapper are in the org.thymeleaf.spring3.* or org.thymeleaf.spring4.* packages,
        // depending on the version of Spring we are using.

        if (ExpressionUtils.evaluateAsBoolean(expressionObject, evaluationContext)) {
            return true;
        }
        return false;

    }


    @SuppressWarnings("unchecked")
    private static SecurityExpressionHandler<FilterInvocation> getExpressionHandler(final ServletContext servletContext) {

        final ApplicationContext ctx = getContext(servletContext);

        final Map<String, SecurityExpressionHandler> expressionHandlers =
                ctx.getBeansOfType(SecurityExpressionHandler.class);

        for (SecurityExpressionHandler handler : expressionHandlers.values()) {
            if (FilterInvocation.class.equals(GenericTypeResolver.resolveTypeArgument(handler.getClass(), SecurityExpressionHandler.class))) {
                return handler;
            }
        }

        throw new TemplateProcessingException(
                "No visible SecurityExpressionHandler instance could be found in the application " +
                        "context. There must be at least one in order to support expressions in Spring Security " +
                        "authorization queries.");

    }


    public static boolean authorizeUsingUrlCheck(
            final String url, final String method, final Authentication authentication,
            final HttpServletRequest request, final ServletContext servletContext) {


        final boolean result =
                getPrivilegeEvaluator(servletContext, request).isAllowed(
                        request.getContextPath(), url, method, authentication) ?
                        true : false;


        return result;

    }


    private static WebInvocationPrivilegeEvaluator getPrivilegeEvaluator(
            final ServletContext servletContext, final HttpServletRequest request) {

        final WebInvocationPrivilegeEvaluator privEvaluatorFromRequest =
                (WebInvocationPrivilegeEvaluator) request.getAttribute(WebAttributes.WEB_INVOCATION_PRIVILEGE_EVALUATOR_ATTRIBUTE);
        if (privEvaluatorFromRequest != null) {
            return privEvaluatorFromRequest;
        }

        final ApplicationContext ctx = getContext(servletContext);

        final Map<String, WebInvocationPrivilegeEvaluator> privilegeEvaluators =
                ctx.getBeansOfType(WebInvocationPrivilegeEvaluator.class);

        if (privilegeEvaluators.size() == 0) {
            throw new TemplateProcessingException(
                    "No visible WebInvocationPrivilegeEvaluator instance could be found in the application " +
                            "context. There must be at least one in order to support URL access checks in " +
                            "Spring Security authorization queries.");
        }

        return (WebInvocationPrivilegeEvaluator) privilegeEvaluators.values().toArray()[0];

    }


    public static ApplicationContext getContext(final ServletContext servletContext) {
        return SpringSecurityWebApplicationContextUtils.findRequiredWebApplicationContext(servletContext);
    }


}
